#pragma once
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <signal.h>
// 下面四个是套接字编程基本头文件
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "ThreadPool.hpp"
#include "Log.hpp"
#include "Task.hpp"
#include "daemon.hpp"

extern Log log;

const int defaultfd = -1; // 套接字初始化为-1
const std::string defaultip = "0.0.0.0";
const int backlog = 10; // 这个是listen第二个参数，一般设置的时候不要设置太大，该参数和Tcp协议内部的一个等待队列有关，目前只要知道这个队列不能太长就可以了，以后详细解释Tcp协议时会讲解

enum
{
    UsageError = 1,
    SOCKET_ERR,
    BIND_ERR,
    ListenError
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *t)
        : sockfd(fd), clientip(ip), clientport(port), tsvr(t)
    {
    }

public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port = 8888, const std::string &ip = defaultip)
        : _listensockfd(defaultfd), _port(port), _ip(ip)
    {
    }

    void InitServer()
    {
        // 1，创建Tcp套接字
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0); // SOCK_STREAM表示可靠的，双向的基于连接的字节流服务，就是Tcp协议
        if (_listensockfd < 0)                           // 创建失败
        {
            log(Fatal, "listensocket create error: %d, errorstring: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
        log(Info, "listensocket create success,_listensockfd: %d", _listensockfd); // 创建成功，输出日志

        int opt = 1;
        setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器挂掉后无法立即重启，（Tcp协议理论再详细了解）

        // 2，绑定
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);

        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); //这两个的效果一样，把字符串转四字节
        inet_aton(_ip.c_str(), &(local.sin_addr));

        // local.sin_addr.s_addr = INADDR_ANY;

        int n = bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            log(Fatal, "bind errno: %d, errorstring: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        log(Info, "bind success, errno: %d, errorstring: %s", errno, strerror(errno));
        // Tcp是面向连接的，所以服务器一般是比较“被动的”，必须随时应为来自客户端的请求，所以服务器要一直处于一种等待连接到来的状态
        // 3，listen监听（man 2 listen）：表示将套接字设置为监听状态，成功返回0，错误返回-1，错误码被设置
        if (listen(_listensockfd, backlog) < 0)
        {
            log(Fatal, "listen error: %d, errorstring: %s", errno, strerror(errno));
            exit(ListenError);
        }
        log(Info, "listen success, errno: %d, errorstring: %s", errno, strerror(errno));
    }

    static void *Routine(void *args) // 需要是静态的，因为线程执行函数规定的
    {
        // 所以静态函数不能访问类内非静态成员变量，所以在线程传参的时候，把类的this指针也传进去，就可以了
        // 是为了方便线程分离才在类内实现的Routine
        pthread_detach(pthread_self()); // 直接分离线程，因为后面主线程需要一直获取新连接，创建出进程就不管了，所以不能有join，所以只能分离线程
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->clientip, td->clientport);
        delete td;
        return nullptr;
    }

    void Start()
    {
        Daemon(); // 守护进程化，进来是父进程，出来是子进程变成的守护进程
        // daemon(0,0); 系统调用实现守护进程
        ThreadPool<Task>::GetInstance()->Start(); // 启动线程池

        // signal(SIGCHLD, SIG_IGN);

        log(Info, "tcpserver is running...");
        while (true)
        {
            // 1，获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listensockfd, (struct sockaddr *)&client, &len);
            // 问题：Udp只要一个套接字，为啥Tcp有上面的sockfd和类的_listensockfd两个甚至以后会有多个呢？
            //_listensockfd核心工作就是在底层获取新的连接，真正提供数据通信服务的，是accept返回的sockfd。所以我们会有两个套接字，获取新链接的套接字叫做“监听套接字”
            if (sockfd < 0)
            {
                log(Warning, "accept errno: %d, errorstring: %s", errno, strerror(errno));
                continue; // 一次获取失败就继续获取
            }
            uint16_t clientport = ntohs(client.sin_port); // 获取客户端port、
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip)); // 获取客户端的ip地址
            // 2，根据新连接来进行通信
            log(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, clientip, clientport);
            // ①：单进程------------------------------------------
            // Service(sockfd, clientip, clientport); // 给连接过来的ip进行服务
            //  问题：1，客户端退了服务器咋办  2，客户端断线了咋办
            // close(sockfd);

            // ②：多进程------------------------------------------
            // pid_t id = fork();
            // if (id == 0) // 子进程
            // {
            // close(_listensockfd); // sockfd是前面打开的描述符，所以正常情况下_listensockfd子进程用不到，所以可以关闭
            // if (fork() > 0)       // 创建孙子进程
            // {
            // 父进程
            //  exit(0);
            // 这样一写，后面的wait等待就不会被阻塞了，因为在子进程里面又fork了一次，这个父进程退了相当于子进程退了,
            // 但是这个小的父进程的子进程没有退，所以到下面的代码时，其实是孙子进程最后提供的服务
            //}
            // 孙子进程，而孙子继承的父进程直接挂掉了，最后就会被系统“领养”，最后被系统自动回收
            // Service(sockfd, clientip, clientport); // 给连接过来的ip进行服务
            // close(sockfd);
            // exit(0);
            // }
            // else // 父进程
            // {
            // 前面父进程获取到的sockfd已经给子进程继承下去给子进程用了，所以父进程就不再关心sockfd了，
            // 和子进程不关心_listensockfd一样，如果不关就会导致系统里面有很多打开的文件没有关，所以要关闭不必要的文件描述符
            // close(sockfd);
            // 这个步骤和管道重定向有相似之处，可以重复关，因为会有引用计数，关掉了只是把计数-1
            // pid_t rid = waitpid(id, nullptr, 0); // 阻塞等待，但是阻塞不满足要求，所以有了孙子进程
            // (void)rid;                           // 可以直接用signal忽略，取消等待
            //}

            // ③：多线程
            // 创建进程是需要代价的，所以多进程版了解一下即可，实际开发中不会用多进程模式去搞，一般都是用线程去搞
            // ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, Routine, td);
            // 多线程这里不能和多进程那样关闭文件描述符，因为所有的线程都公用的一个当前进程的文件描述符表

            // 多线程版本虽然可以了，但还是有缺陷：
            // ④：线程池 ---------------------
            Task t(sockfd, clientip, clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }

    // 单进程服务，两个主机同时连接，但是服务器只能给一台主机提供服务，这种主机直接pass
    // 所以用多线程，更进一步直接用线程池
    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        // 测试
        char buffer[4096];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer)); // 也可以用文件的接口从sockfd里面读数据
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size()); // 也可以用文件的接口往sockfd写回数据
            }
            else if (n == 0) // 客户端退出会关闭套接字，那么read会读出错，返回值n会赋值为0
            {
                log(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else // 读取出错
            {
                log(Warning, "read error, sockfd: %d, client port: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }

    ~TcpServer() {}

private:
    int _listensockfd; // 套接字
    uint16_t _port;    // 端口号
    std::string _ip;   // IP
};